#include "Core/Renderer.h"
#include "Core/Device.h"
#include "Debug/Logger.h"
#include "GL/glew.h"
#include "GL/glfw.h"

namespace blue {

SINGLETON_IMPL(Renderer)

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

Renderer::Renderer()
{
}

Renderer::~Renderer()
{
}

void Renderer::init(float near, float far, int fov, Color3i bgColor, int swapInterval)
{
    _size = Vector2i(Device::instance()->getSize().X, Device::instance()->getSize().Y);

    _near = near;
    _far = far;
    _fov = fov;
    _bgColor = bgColor;
    _indexBuffer = NULL;
    _activeShader = NULL;
    _start_time = 0;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(_fov, (double)_size.X/_size.Y, _near, _far);
    glEnable(GL_DEPTH_TEST);
    glColor3ub(_bgColor.R, _bgColor.G, _bgColor.B);

    glEnable(GL_CULL_FACE);
    glPolygonMode(GL_FRONT, GL_FILL);

    glfwSwapInterval(swapInterval);
}

void Renderer::resize(int width, int height)
{
    _size = Vector2i(width, height);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, _size.X, _size.Y);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(_fov, (double)_size.X/_size.Y, _near, _far);
}

bool Renderer::initExtensions()
{
    GLenum code = glewInit();
    if(code != GLEW_OK)
    {
        Logger::log() << "Couldn't initialize extensions : " << glewGetErrorString(code) << "\n";
        return false;
    }
    else return true;
}

bool Renderer::checkExtension(const char *name)
{
    if(glewIsSupported(name)) return true;
    else return false;
}

float Renderer::getNear() const
{
    return _near;
}

float Renderer::getFar() const
{
    return _far;
}

int Renderer::getFov() const
{
    return _fov;
}

Vector2i Renderer::getSize() const
{
    return _size;
}

Color3i Renderer::getBgColor() const
{
    return _bgColor;
}

void Renderer::set2DMode()
{
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, _size.X, 0, _size.Y);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
}

void Renderer::end2DMode()
{
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glEnable(GL_DEPTH_TEST);
}

void Renderer::setPolygonMode(GLenum face, GLenum mode)
{
    glPolygonMode(face, mode);
}

void Renderer::lookAt(Vector3d cam, Vector3d target, Vector3d axes)
{
    gluLookAt(cam.X, cam.Y, cam.Z,
              target.X, target.Y, target.Z,
              axes.X, axes.Y, axes.Z);
}

void Renderer::beginScene(Color4f color)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glClearColor(color.R, color.G, color.B, color.A);
}

void Renderer::endScene()
{
    glfwSwapBuffers();
}

void Renderer::setMatrix(GLenum mode)
{
    glMatrixMode(mode);
}

void Renderer::pushMatrix()
{
    glPushMatrix();
}

void Renderer::popMatrix()
{
    glPopMatrix();
}

void Renderer::scale(const Vector3d &scale)
{
    glScaled(scale.X, scale.Y, scale.Z);
}

void Renderer::translate(const Vector3d &pos)
{
    glTranslated(pos.X , pos.Y , pos.Z);
}

void Renderer::rotate(GLdouble angle, const Vector3d &axes)
{
    glRotated(angle, axes.X, axes.Y, axes.Z);
}

void Renderer::setVertexBuffer(IBufferBase *buffer)
{
    if(buffer != NULL)
    {
        glBindBufferARB(buffer->getType(), buffer->getBuffer());

        for(int i=0; i<buffer->getNbDeclarations(); i++)
        {
            switch(buffer->getDeclaration(i).usage)
            {
                case GL_VERTEX_ARRAY: glVertexPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                case GL_NORMAL_ARRAY: glNormalPointer(GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                case GL_COLOR_ARRAY: glColorPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                case GL_INDEX_ARRAY: glIndexPointer(GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                case GL_EDGE_FLAG_ARRAY: glEdgeFlagPointer(GL_FLOAT, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
                default: break;
            }
            glEnableClientState(buffer->getDeclaration(i).usage);
        }
    }
    else
    {
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_INDEX_ARRAY);
        glDisableClientState(GL_EDGE_FLAG_ARRAY);

        glBindBufferARB(GL_ARRAY_BUFFER, 0);
    }
}

void Renderer::setIndexBuffer(IBufferBase *buffer)
{
    if(buffer != NULL)
    {
        _indexBuffer = buffer;
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, buffer->getBuffer());
    }
    else
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void Renderer::drawPrimitives(GLenum mode, GLsizei count, GLint first)
{
    glDrawArrays(mode, first, count);
}

void Renderer::drawIndexedPrimitives(GLenum mode, GLsizei count)
{
    glDrawElements(mode, count, GL_UNSIGNED_INT, BUFFER_OFFSET(_indexBuffer->getDeclaration(0).first));
}

void Renderer::setTexture(ITexture *texture)
{
    if(texture != NULL)
    {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texture->getGLID());
    }
    else
    {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

void Renderer::setMultiTexture(ITexture *texture, int unit)
{
    if(texture != NULL)
    {
        switch(unit)
        {
            case 0:
                glActiveTextureARB(GL_TEXTURE0_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, texture->getGLID());
                break;
            case 1:
                glActiveTextureARB(GL_TEXTURE1_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, texture->getGLID());
                break;
            case 2:
                glActiveTextureARB(GL_TEXTURE2_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, texture->getGLID());
                break;
            case 3:
                glActiveTextureARB(GL_TEXTURE3_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, texture->getGLID());
                break;
            default:
                glActiveTextureARB(GL_TEXTURE0_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, texture->getGLID());
                break;
        }
    }
    else
    {
        switch(unit)
        {
            case 0:
                glActiveTextureARB(GL_TEXTURE0_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, 0);
                break;
            case 1:
                glActiveTextureARB(GL_TEXTURE1_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, 0);
                break;
            case 2:
                glActiveTextureARB(GL_TEXTURE2_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, 0);
                break;
            case 3:
                glActiveTextureARB(GL_TEXTURE3_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, 0);
                break;
            default:
                glActiveTextureARB(GL_TEXTURE0_ARB);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, 0);
                break;
        }
    }
}

void Renderer::setShader(Shader *shader)
{
    if(shader)
      {
	_activeShader = shader;
	glUseProgramObjectARB(shader->getProgram());
      }
    else
      {
	_activeShader = NULL;
	glUseProgramObjectARB(0);
      }
}

Shader *Renderer::getShader() const
{
  return _activeShader;
}

}
